((window) => {
  // 定义webrtc对象，类似java中的对象
  let Webrtc = function (socket) {

    // 定义RTCPeerConnection
    let pc = null;


    // webrtc数据存储
    let webrtcData = {
      // 回调函数
      callbackOnRemoteStream: null,
      // 配置信息
      config: {
        // 连接类型,默认语音，视频shipin-call
        type: '',
        // 媒体约束
        constraints: {},
        // 发起方唯一标识
        from: '',
        // 接收方唯一标识
        to: ''
      },
      // 本地媒体流
      localMediaStream: null,
      // 远端媒体流
      remoteMediaStream: null
    }


    // 创建pc
    let createRTCPeerConnection = function () {
      pc = new RTCPeerConnection({
        // stun/turn  服务器地址
        iceServers: [
          {
            "url": "stun:stun.l.google.com:19302",
          },
          {
            "url": "turn:ouyunc.com:3478",
            "username": "coturn",
            "credential": "coturn"
          }
        ]
      })
    }

    // 获取webrtc数据信息
    this.getWebrtcData = () => {
      return webrtcData;
    }

    // 释放资源
    this.releaseSource = () => {
      console.log('挂断释放资源');

      if (pc) {
        pc.ontrack = null;
        pc.onremovetrack = null;
        pc.onremovestream = null;
        pc.onicecandidate = null;
        pc.oniceconnectionstatechange = null;
        pc.onsignalingstatechange = null;
        pc.onicegatheringstatechange = null;
        pc.onnegotiationneeded = null;


        if (webrtcData.localMediaStream) {
          webrtcData.localMediaStream.getTracks().forEach(track => track.stop());
        }

        if (webrtcData.remoteMediaStream) {
          webrtcData.remoteMediaStream.getTracks().forEach(track => track.stop());
        }
        pc.close();
        pc = null;
      }
      webrtcData = {
        // 回调函数
        callbackOnRemoteStream: null,
        // 配置信息
        config: {
          type: 'yuyin-call',
          // 媒体约束
          constraints: {},
          // 发起方唯一标识
          from: '',
          // 接收方唯一标识
          to: ''
        },
        // 本地媒体流
        localMediaStream: null,
        // 远端媒体流
        remoteMediaStream: null
      }
    }

    // caller/callee 挂断音频/视频
    this.hungup = () => {
      this.releaseSource()
    }


    // 设置麦克风状态：1-开启，0-关闭
    this.setMicrophone = (status) => {

    }

    // 设置扬声器状态：1-开启，0-关闭
    this.setLoudspeaker = (status) => {

    }


    // callee(接收方) 收到远端offer
    this.addRemoteOffer = (offerSdp) => {
      console.log("接收方==>将收到远端的offer sdp设置到连接上")
      pc.setRemoteDescription(new RTCSessionDescription(offerSdp))
        .then(() => {
          console.log("接收方==> 将本地媒体流轨道添加到pc连接上")
          webrtcData.localMediaStream.getTracks().forEach(track => pc.addTrack(track, webrtcData.localMediaStream));
          console.log("接收方==> 创建answer")
          pc.createAnswer()
            .then((answer) => {
              console.log("接收方==> 设置本地answer sdp")
              return pc.setLocalDescription(answer);
            }).then(() => {
              //媒体协商： 通过socket 给需要建立的对端发送sdp,格式自定义
              console.log("接收方==> 发送本地answer sdp 给发送方")
              let answerMessage = new proto.com.ouyu.im.Message();
              answerMessage.setFrom(webrtcData.config.from)
              answerMessage.setTo(webrtcData.config.to)
              answerMessage.setContentType(SOCKET_CONSTANT.PACKET.MESSAGE_CONTENT_TYPE.TEXT_CONTENT);
              answerMessage.setContent(JSON.stringify({
                type: "answer",
                sdp: pc.localDescription
              }));
              // 时间戳毫秒
              answerMessage.setCreateTime(new Date().getTime());
              socket.send(answerMessage, SOCKET_CONSTANT.PACKET.MESSAGE_TYPE.WEBRTC_SIGNALING);
            }).catch(handleGetUserMediaError);
        });

    }

    // 发送方设置远端answer sdp
    this.addRemoteAnswer = (answerSdp) => {
      if (pc) {
        console.log("发送方==>将收到远端的answer sdp设置到连接上")
        pc.setRemoteDescription(new RTCSessionDescription(answerSdp))
      } else {
        console.error("RTCPeerConnection not found!")
      }
    }

    this.addIceCandidate = (candidateSdp) => {
      if (pc) {
        console.info("添加ice候选者", candidateSdp)
        pc.addIceCandidate(new RTCIceCandidate(candidateSdp))
      } else {
        console.error("RTCPeerConnection not found!")
      }
    }

    // 处理getUserMedia 发生错误的方法
    let handleGetUserMediaError = function (err) {
      switch (err.name) {
        case "NotFoundError":
          alert("Unable to open your call because no camera and/or microphone were found.");
          break;
        case "SecurityError":
        case "PermissionDeniedError":
          alert("用户拒绝了使用权限!")
          break;
        default:
          alert("Error opening your camera and/or microphone: " + err.message);
          break;
      }
      // 由于调取getUserMedia失败释放资源
      this.releaseSource();
    }





    //当调用者创建了其 RTCPeerConnection ，创建了媒体流，并将其磁道添加到连接中时，就会触发该事件进行创建offer
    let handleOnnegotiationneededEventCallee = function () {
      console.log("接收方==>开始进行媒体协商")
    }

    let handleICECandidateEventCallee = function (e) {
      console.log("接收方==>触发本地ice 候选者收集，并且采集到相关iceCandidate数据")
      let iceCandidateSdp = e.candidate;
      if (iceCandidateSdp) {
        console.log("接收方==>发送本地iceCandidate相关数据给远端peer")
        // 交换ice候选者
        let iceCandidateMessage = new proto.com.ouyu.im.Message();
        iceCandidateMessage.setFrom(webrtcData.config.from)
        iceCandidateMessage.setTo(webrtcData.config.to)
        iceCandidateMessage.setContentType(SOCKET_CONSTANT.PACKET.MESSAGE_CONTENT_TYPE.TEXT_CONTENT);
        iceCandidateMessage.setContent(JSON.stringify({
          type: "ice-candidate",
          sdp: iceCandidateSdp
        }));
        // 时间戳毫秒
        iceCandidateMessage.setCreateTime(new Date().getTime());
        socket.send(iceCandidateMessage, SOCKET_CONSTANT.PACKET.MESSAGE_TYPE.WEBRTC_SIGNALING);
      }
    }


    //事件iceconnectionstatechange当连接状态更改时（例如，当从另一端终止调用时），由ICE层将事件发送到RTCPeerConnection 。
    let handleICEConnectionStateChangeEventCallee = function (event) {
      console.log("接收方==>ice 连接状态改变了", pc.iceConnectionState)

      switch (pc.iceConnectionState) {
        case "closed":
        case "failed":
        case "disconnected":
          this.releaseSource();
          break;
      }
    }

    //如果信号状态变为 closed，关闭呼叫。
    let handleSignalingStateChangeEventCallee = function (e) {
      console.log("接收方==>ice 信令状态改变了", pc.signalingState)
      switch (pc.signalingState) {
        case "closed":
          this.releaseSource();
          break;
      }
    }
    // peer连接状态改变
    let handlePeerConnectionStateEventCallee = function (e) {
      console.log('接收方===》peer 连接状态改变了,%o', pc.connectionState);
    }

    // 连接建立的回调函数,远端流
    let handleTrackEventCallee = function (e) {
      webrtcData.remoteMediaStream = e.streams[0]
      console.log("接收方==>************接收到远端的媒体流************", e.streams)
      webrtcData.callbackOnRemoteStream(webrtcData.localMediaStream, e.streams);
      // 这里应该抛出去，给外部使用
    }

    // 发起方移除媒体轨道会触发改事件
    let handleRemoveTrackEventCallee = function (e) {
      console.log("接收方==>监听到移除媒体轨道事件", e.streams)
    }




    //当调用者创建了其 RTCPeerConnection ，创建了媒体流，并将其磁道添加到连接中时，就会触发该事件进行创建offer
    let handleOnnegotiationneededEventCaller = function () {
      console.log("发起方==>开始进行媒体协商")

      console.log("发起方==>创建offer")
      pc.createOffer()
        .then((offer) => {
          console.log("发起方==>设置本地offer sdp", offer)
          // 这里设置本地offer,当执行的时候就触发ice 候选者收集，候选者收集完后就会触发handleICECandidateEvent
          return pc.setLocalDescription(offer);
        })
        .then(() => {
          console.log("发起方==> 将本地offer 发送给远端peer")
          //媒体协商： 通过socket 给需要建立的对端发送sdp,格式自定义
          let offerMessage = new proto.com.ouyu.im.Message();
          offerMessage.setFrom(webrtcData.config.from)
          offerMessage.setTo(webrtcData.config.to)
          offerMessage.setContentType(SOCKET_CONSTANT.PACKET.MESSAGE_CONTENT_TYPE.TEXT_CONTENT);
          offerMessage.setContent(JSON.stringify({
            type: "offer",
            sdp: pc.localDescription
          }));
          // 时间戳毫秒
          offerMessage.setCreateTime(new Date().getTime());
          socket.send(offerMessage, SOCKET_CONSTANT.PACKET.MESSAGE_TYPE.WEBRTC_SIGNALING);
        })
        .catch((err) => {
          // An error occurred, so handle the failure to connect
          console.error("发起方==> create offer fail!", err);
          // 关闭处理已创建的
          this.releaseSource()
        });
    }


    let handleICECandidateEventCaller = function (e) {
      console.log("发起方==>触发本地ice 候选者收集，并且采集到相关iceCandidate数据")
      let iceCandidateSdp = e.candidate;
      if (iceCandidateSdp) {
        console.log("发起方==>发送本地iceCandidate相关数据给远端peer")
        // 交换ice候选者
        let iceCandidateMessage = new proto.com.ouyu.im.Message();
        iceCandidateMessage.setFrom(webrtcData.config.from)
        iceCandidateMessage.setTo(webrtcData.config.to)
        iceCandidateMessage.setContentType(SOCKET_CONSTANT.PACKET.MESSAGE_CONTENT_TYPE.TEXT_CONTENT);
        iceCandidateMessage.setContent(JSON.stringify({
          type: "ice-candidate",
          sdp: iceCandidateSdp
        }));
        // 时间戳毫秒
        iceCandidateMessage.setCreateTime(new Date().getTime());
        socket.send(iceCandidateMessage, SOCKET_CONSTANT.PACKET.MESSAGE_TYPE.WEBRTC_SIGNALING);
      }
    }


    //事件iceconnectionstatechange当连接状态更改时（例如，当从另一端终止调用时），由ICE层将事件发送到RTCPeerConnection 。
    let handleICEConnectionStateChangeEventCaller = function (event) {
      console.log("发起方==>ice 连接状态改变了", pc.iceConnectionState)

      switch (pc.iceConnectionState) {
        case "closed":
        case "failed":
        case "disconnected":
          this.releaseSource();
          break;
      }
    }

    //如果信号状态变为 closed，关闭呼叫。
    let handleSignalingStateChangeEventCaller = function (e) {
      console.log("发起方==>ice 信令状态改变了", pc.signalingState)

      switch (pc.signalingState) {
        case "closed":
          this.releaseSource();
          break;
      }
    }
    // peer连接状态改变
    let handlePeerConnectionStateEventCaller = function (e) {
      console.log('发起方 peer 连接状态改变了,%o', pc.connectionState);
    }
    // 连接建立的回调函数,远端流
    let handleTrackEventCaller = function (e) {
      // 设置媒体流，可能是多路流这里只设置一路
      webrtcData.remoteMediaStream = e.streams[0]
      // 获取多个媒体流，这里应该将流抛出去
      console.log("发起方==>************接收到远端的媒体流************", e.streams)
      webrtcData.callbackOnRemoteStream(webrtcData.localMediaStream, e.streams);
    }

    // 媒体轨道移除触发的事件
    let handleRemoveTrackEventCaller = function (e) {
      console.log("发起方==>监听到移除媒体轨道事件", e.streams)
    }





    // 当收到callee 的同意接听后进行调用
    this.start = () => {
      if (pc) {
        var localMediaStream = webrtcData.localMediaStream;
        // 这里会触发相关媒体协商事件
        localMediaStream.getTracks().forEach(track => {
          pc.addTrack(track, localMediaStream);
        });
      } else {
        console.error("peer连接不可用！请释放资源");
      }


    }

    // 接收方准备接收调用方的数据
    this.receive = (webrtcConfig, callback, callback2) => {
      webrtcData.config = webrtcConfig;
      webrtcData.callbackOnRemoteStream = callback2;
      if (pc) {
        // 回应远端当前媒体设备正在使用、请稍后重试！
        //socket.send()
        return;
      }
      console.log("接收方==>创建媒体流")
      navigator.mediaDevices.getUserMedia(webrtcData.config.constraints)
        .then((mediaStream) => {
          // 设置媒体流
          webrtcData.localMediaStream = mediaStream;
          console.log("接收方==>创建peer连接")
          // 创建pc
          createRTCPeerConnection()
          pc.ontrack = handleTrackEventCallee
          // 有轨道移除时
          pc.onremovetrack = handleRemoveTrackEventCallee
          pc.onicecandidate = handleICECandidateEventCallee
          // 媒体协商
          pc.onnegotiationneeded = handleOnnegotiationneededEventCallee
          // 监听状态变更
          pc.oniceconnectionstatechange = handleICEConnectionStateChangeEventCallee;
          pc.onsignalingstatechange = handleSignalingStateChangeEventCallee;
          pc.onconnectionstatechange = handlePeerConnectionStateEventCallee;
          // 回调函数,将webrtc 对象传递回去
          callback(this);
        })
        .catch(handleGetUserMediaError);

    }


    // 调用通话，进行预处理
    this.call = (webrtcConfig, callback, callback2) => {
      // 给全局赋值
      webrtcData.config = webrtcConfig;
      webrtcData.callbackOnRemoteStream = callback2;
      navigator.mediaDevices.getUserMedia(webrtcData.config.constraints)
        .then((mediaStream) => {
          // console.log("发起方==>发起呼叫", mediaStream)
          // 设置媒体流

          webrtcData.localMediaStream = mediaStream
          // 创建连接
          console.log("发起方==>创建peer连接")

          createRTCPeerConnection()

          // 事件监听当对等连接建立时会有轨道添加进来，回调该方法
          console.log("发起方==>设置连接上的监听回调")
          pc.ontrack = handleTrackEventCaller
          // 有轨道移除时
          pc.onremovetrack = handleRemoveTrackEventCaller
          pc.onicecandidate = handleICECandidateEventCaller
          // 媒体协商
          pc.onnegotiationneeded = handleOnnegotiationneededEventCaller
          // 监听状态变更
          pc.oniceconnectionstatechange = handleICEConnectionStateChangeEventCaller;
          pc.onsignalingstatechange = handleSignalingStateChangeEventCaller;
          pc.onconnectionstatechange = handlePeerConnectionStateEventCaller;

          // 发送电话信号给对方
          console.log("发起方==>发送电话信号给对方")
          let callMessage = new proto.com.ouyu.im.Message();
          callMessage.setFrom(webrtcData.config.from)
          callMessage.setTo(webrtcData.config.to)
          callMessage.setContentType(SOCKET_CONSTANT.PACKET.MESSAGE_CONTENT_TYPE.TEXT_CONTENT);
          callMessage.setContent(JSON.stringify({
            type: webrtcData.config.type,
          }));
          callMessage.setCreateTime(new Date().getTime());
          socket.send(callMessage, SOCKET_CONSTANT.PACKET.MESSAGE_TYPE.WEBRTC_SIGNALING);

          // 回调函数,将webrtc 对象传递回去
          callback(this);
        })
        .catch(handleGetUserMediaError);
    }





  }





  // 初始化webrtc对象，作为入口
  Webrtc.init = (socket) => {
    return new Webrtc(socket);
  };


  window.Webrtc = Webrtc
})(window)